/*************************************************************************
 *
 *   file		: Aura.cs
 *   copyright		: (C) The WCell Team
 *   email		: info@wcell.org
 *   last changed	: $LastChangedDate: 2009-09-26 22:15:07 +0100 (Sat, 26 Sep 2009) $
 *   last author	: $LastChangedBy: dominikseifert $
 *   revision		: $Rev: 1115 $
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NLog;
using WCell.Constants;
using WCell.Constants.Spells;
using WCell.Core.Timers;
using WCell.RealmServer.Entities;
using WCell.RealmServer.Misc;

namespace WCell.RealmServer.Spells.Auras
{
	/// <summary>
	/// An Aura is any kind of long-lasting passive effect or buff.
	/// Some can be seen as an icon below the Player's status bar.
	/// 
	/// TODO: Repeating auras that require Caster info vanish on logout
	/// TODO: Some auras add an emotestate (eg eating etc)
	/// 
	/// If MultipleApplications, just stack up the effect and Apply each Charge on each tick
	/// 
	/// </summary>
	public partial class Aura : IAura, IUpdatable, IProcHandler
	{
		private static Logger log = LogManager.GetCurrentClassLogger();

		public static readonly Aura[] EmptyArray = new Aura[0];

		public static readonly IEnumerator<Aura> EmptyEnumerator = new AuraEnumerator();

		#region Fields
		public readonly AuraIndex Id;
		protected AuraCollection m_auras;
		protected CasterInfo m_casterInfo;
		protected Spell m_spell;
		protected bool m_hideFromClient;
		protected IEnumerable<AuraEffectHandler> m_handlers;
		protected bool m_beneficial;
		/// <summary>
		/// The controlling Timer (eg a SpellChannel) or null if self-controlled
		/// </summary>
		protected ITickTimer m_controller;

		protected int m_RemainingCharges;
		protected int m_startTime;
		protected int m_duration;
		protected int m_amplitude;
		protected int m_ticks;
		protected int m_maxTicks;
		TimerEntry m_timer;

		protected byte m_index;
		protected AuraFlags m_auraFlags;
		protected byte m_auraLevel;
		#endregion

		protected Aura()
		{
			IsActive = true;
		}

		/// <summary>
		/// Creates a new Aura
		/// </summary>
		/// <param name="auras"></param>
		/// <param name="caster">Information about who casted</param>
		/// <param name="spell">The spell that this Aura represents</param>
		/// <param name="handlers">All handlers must have the same AuraUID</param>
		internal Aura(AuraCollection auras, CasterInfo caster, Spell spell,
			IEnumerable<AuraEffectHandler> handlers, byte index, bool beneficial, int amplitude)
		{
			IsActive = true;

			m_auras = auras;
			Id = new AuraIndex
			{
				AuraUID = spell.AuraUID,
				IsPositive = beneficial
			};
			m_casterInfo = caster;
			m_spell = spell;
			m_hideFromClient = !m_spell.ShouldShowToClient();//m_spell.IsPassive;
			m_handlers = handlers;

			// figure out whether it is positive or negative
			m_beneficial = beneficial;

			m_amplitude = amplitude;
			m_index = index;
			m_auraLevel = (byte)caster.Level;

			DetermineFlags();
		}

		private void DetermineFlags()
		{
			m_auraFlags = m_spell.DefaultAuraFlags;

			if (m_auras.Owner.EntityId == m_casterInfo.CasterId)
			{
				m_auraFlags |= AuraFlags.TargetIsCaster;
			}

			if (m_beneficial)
			{
				m_auraFlags |= AuraFlags.Positive;
			}
			else
			{
				m_auraFlags |= AuraFlags.Negative;
			}

			if (m_spell.Durations.Max > 0)
			{
				m_auraFlags |= AuraFlags.HasDuration;
			}

			foreach (var handler in m_handlers)
			{
				m_auraFlags |= (AuraFlags)(1 << handler.SpellEffect.EffectIndex);
			}
		}

		#region Properties
		/// <summary>
		/// The <c>AuraCollection</c> of the Unit that owns this Aura.
		/// </summary>
		public AuraCollection Auras
		{
			get
			{
				return m_auras;
			}
		}

		/// <summary>
		/// The Spell that belongs to this Aura
		/// </summary>
		public Spell Spell
		{
			get { return m_spell; }
		}

		public bool IsActive
		{
			get;
			protected set;
		}

		public bool CanBeCancelled
		{
			get { return m_spell != null && m_beneficial && !m_spell.Attributes.Has(SpellAttributes.CannotRemove); }
		}

		/// <summary>
		/// TODO: Whether this Aura has at least one positive Effect
		/// </summary>
		public bool IsBeneficial
		{
			get
			{
				return m_beneficial;
			}
		}

		/// <summary>
		/// The amount of ticks left (always 0 for non-periodic auras)
		/// </summary>
		public int TicksLeft
		{
			get
			{
				return MaxTicks - Ticks;
			}
		}

		/// <summary>
		/// Information about the caster
		/// </summary>
		public CasterInfo CasterInfo
		{
			get
			{
				return m_casterInfo;
			}
		}

		/// <summary>
		/// The actual Caster (might be null if caster went offline)
		/// </summary>
		public WorldObject Caster
		{
			get
			{
				return m_casterInfo.Caster;
			}
		}

		/// <summary>
		///  The amplitude between aura-ticks (only for non-passive auras which are not channeled)
		/// </summary>
		public int Amplitude
		{
			get
			{
				return m_amplitude;
			}
		}

		/// <summary>
		/// Whether this Aura is not visible to the client (only its effects will make him realize it)
		/// </summary>
		public bool IsVisible
		{
			get
			{
				return !m_hideFromClient;
			}
		}

		/// <summary>
		/// The maximum amount of Applications for this Aura
		/// </summary>
		public int MaxApplications
		{
			get
			{
				return m_spell.MaxStackCount;
			}
		}

		/// <summary>
		/// The position of this Aura within the client's Aura-bar (0 if not exposed to client)
		/// </summary>
		public byte Index
		{
			get
			{
				return m_index;
			}
			set
			{
				RemoveFromClient();
				m_index = value;
				ExposeToClient();
			}
		}

		/// <summary>
		/// The time that is left until this Aura disbands in ms.
		/// Auras's without timeout can't be resetted.
		/// Channeled Auras are controlled by the holding SpellChannel.
		/// Returns a negative value if Aura doesn't has no timeout (or already expired).
		/// </summary>
		public int TimeLeft
		{
			get
			{
				if (m_controller == null)
				{
					return m_duration - (Environment.TickCount - m_startTime);
				}
				return m_controller.TimeLeft;
			}
			set
			{
				if (HasTimer)
				{
					// figure out amplitude
					m_startTime = Environment.TickCount;
					if (m_amplitude > 0)
					{
						m_maxTicks = m_duration / m_amplitude;
					}
					else
					{
						m_amplitude = m_duration;
						m_maxTicks = 1;
					}

					m_ticks = 0;

					m_timer.Start(m_amplitude);
				}
			}
		}

		/// <summary>
		/// Whether it is save and legal to steal this Aura (only temporary Auras that are not controlled by a channel or similar)
		/// </summary>
		public bool CanBeStolen
		{
			get
			{
				return m_controller == null && HasTimeout;
			}
		}

		/// <summary>
		/// Whether it is controlled or amplitude > 0
		/// </summary>
		public bool HasTimeout
		{
			get
			{
				return m_amplitude > 0;
			}
		}

		public IEnumerable<AuraEffectHandler> Handlers
		{
			get
			{
				return m_handlers;
			}
		}

		/// <summary>
		/// The controller of this Aura which controls timing, application and removal (such as <see cref="SpellChannel">SpellChannels</see>)
		/// </summary>
		public ITickTimer Controller
		{
			get
			{
				return m_controller;
			}
		}

		/// <summary>
		/// Auras that are not passive and not controlled by a <c>ITickTimer</c> have their own Timers
		/// </summary>
		public bool HasTimer
		{
			get
			{
				//return m_amplitude > 0 && !m_spell.IsPassive && m_controller == null;
				return m_auraFlags.Has(AuraFlags.HasDuration) && m_controller == null;
			}
		}

		public int Ticks
		{
			get
			{
				return m_controller == null ? m_ticks : m_controller.Ticks;
			}
		}

		public int MaxTicks
		{
			get
			{
				return m_controller == null ? m_maxTicks : m_controller.MaxTicks;
			}
		}

		public int Duration
		{
			get
			{
				return m_controller == null ? m_duration : m_controller.Duration;
			}
		}

		public int Until
		{
			get
			{
				if (m_spell.IsPassive)
				{
					return -1;
				}
				else if (m_controller != null)
				{
					return m_controller.Until;
				}
				return Environment.TickCount - m_startTime;
			}
		}

		public byte Level
		{
			get
			{
				return m_auraLevel;
			}
		}

		public AuraFlags Flags
		{
			get { return m_auraFlags; }
		}

		#endregion

		/// <summary>
		/// Method is called 
		/// </summary>
		/// <param name="noTimeout">Whether the Aura should always continue and never expire.</param>
		public void Start(ITickTimer controller, bool noTimeout)
		{
			m_controller = controller;

			if (m_spell.IsProc)
			{
				m_auras.Owner.AddProcHandler(this);
			}

			// figure out and set duration
			if (HasTimer)
			{
				// Aura times itself
				m_timer = new TimerEntry
				{
					Action = Apply
				};

				if (noTimeout)
				{
					m_duration = int.MaxValue;
				}
				else
				{
					m_duration = Spell.GetDuration(m_casterInfo, m_auras.Owner);
				}
				TimeLeft = m_duration;
			}
			else if (controller != null)
			{
				// TODO: review the logic of aura updating after the new packets
				// SendTimeout(controller.Duration);
			}

			// init AuraEffectHandlers
			var handlers = m_handlers;
			foreach (var handler in handlers)
			{
				handler.Init(this);
			}

			m_RemainingCharges = (byte)m_spell.Applications;
			if (m_RemainingCharges > 0 && Caster is Character)
			{
				m_RemainingCharges = ((Character)Caster).PlayerSpells.GetEnhancedInt(SpellEnhancerType.Charges, m_spell, m_RemainingCharges);
			}

			// send to client
			ExposeToClient();

			if (m_spell.HasNonPeriodicAuraEffects)
			{
				// apply initial application, only if not periodic
				foreach (var handler in m_handlers)
				{
					if (!handler.SpellEffect.IsPeriodicAura)
					{
						handler.Apply();
					}
				}
			}

			m_auras.OnAuraChange(this);
			var evt = Initialized;
			if (evt != null)
			{
				evt(this);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		internal void Stack(CasterInfo caster, Spell spell)
		{
			if (IsActive)
			{
				m_casterInfo = caster;

				int oldChargeCount;
				int newChargeCount;

				oldChargeCount = m_RemainingCharges;
				if (m_RemainingCharges < m_spell.MaxStackCount)
				{
					m_RemainingCharges++;
				}
				newChargeCount = m_RemainingCharges;

				TimeLeft = m_spell.GetDuration(caster, m_auras.Owner);

				if (!m_hideFromClient)
				{
					AuraHandler.SendAuraUpdate(m_auras.Owner, this);
				}
			}
		}

		#region Apply & Remove
		/// <summary>
		/// Applies this Aura's effect to its holder
		/// </summary>
		public void Apply()
		{
			Apply(0.0f);
		}

		/// <summary>
		/// Applies one of this Aura's Ticks to its holder
		/// </summary>
		public void Apply(float timeElapsed)
		{
			m_ticks++;

			// if controlled, the Controller decides when the Aura expires
			var expired = (!m_spell.HasPeriodicAuraEffects || m_ticks >= m_maxTicks) && m_controller == null;

			OnApply();
			var count = m_spell.CanStack ? m_RemainingCharges : 1;
			if (!expired || m_spell.HasPeriodicAuraEffects)
			{
				for (var i = 0; i < count; i++)
				{
					foreach (var handler in m_handlers)
					{
						if (m_ticks < 1 || handler.SpellEffect.IsPeriodicAura)
						{
							handler.Apply();
						}

						if (!IsActive)
						{
							// aura got removed by handler
							return;
						}
					}
				}
			}

			if (!m_spell.CanStack && m_spell.ProcCharges > 0)
			{
				//expired = expired || (--m_RemainingCharges == 0);
			}

			if (!expired && m_timer != null)
			{
				m_timer.Start(m_amplitude);
			}

			var evt = Ticked;
			if (evt != null)
			{
				evt(this);
			}

			if (expired)
			{
				Remove(false);
			}
		}

		/// <summary>
		/// Do certain special behavior everytime Aura is applied
		/// </summary>
		private void OnApply()
		{
			if (m_spell.IsFood || m_spell.IsDrink)
			{
				CasterInfo.CasterUnit.Emote(EmoteType.SimpleEat);
			}
		}

		public void Cancel()
		{
			Remove(true);
		}

		public void TryRemove(bool cancelled)
		{
			if (m_spell.IsAreaAura)
			{
				// can only cancel AreaAuras if you are the one causing it or if it can time-out

				var owner = m_auras.Owner;
				if (owner.AreaAura != null && owner.AreaAura.Spell == m_spell)
				{
					owner.AreaAura.Remove(true);
				}
				else if (HasTimeout && Controller == null)
				{
					Remove(true);
				}
			}
			else
			{
				Remove(true);
			}
		}

		/// <summary>
		/// Removes this Aura from the player
		/// </summary>
		/// <param name="cancelled"></param>
		public void Remove(bool cancelled)
		{
			if (IsActive)
			{
				IsActive = false;

				var evt = Removing;
				if (evt != null)
				{
					evt(this);
					// check again, since things might have changed
					if (m_ticks < m_maxTicks || m_controller != null)
					{
						return;
					}
				}

				var auras = m_auras;

				OnRemove(cancelled);

				auras.Cancel(this);
				if (m_controller != null)
				{
					m_controller.OnRemove(auras.Owner, this);
				}
			}
		}

		/// <summary>
		/// Cleans up everything when Aura is removed
		/// </summary>
		protected virtual void OnRemove(bool cancelled)
		{
			RemoveFromClient();
			CallAllHandlers(handler => handler.Remove(cancelled));

			if (m_spell.IsFood)
			{
				m_auras.Owner.StandState = StandState.Stand;
			}

			if (m_auras.Owner.EntityId == m_casterInfo.CasterId &&
				(m_spell.Attributes & SpellAttributes.StartCooldownAfterEffectFade) != 0)
			{
				if (m_auras.Owner is Character)
				{
					//SpellHandler.SendSpellCooldown(m_auras.Owner, ((Character)m_auras.Owner).Client, m_spell.Id, (ushort)m_spell.GetCooldown(m_auras.Owner));
					SpellHandler.SendCooldownUpdate((Character)m_auras.Owner, m_spell.SpellId);
				}
				//m_auras.Owner.Spells.AddCooldown(m_spell, null);
			}
		}
		#endregion


		#region Send Aura information

		protected void ExposeToClient()
		{
			if (m_hideFromClient)
			{
				return;
			}

#if DEBUG
			//log.Info("Sending Aura: " + this);
#endif

			//AuraHandler.SendAuraInfo(m_auras.Owner, (byte)m_index, m_spell.Id, (uint)m_duration, (uint)m_duration);
			AuraHandler.SendAuraUpdate(m_auras.Owner, this);
		}

		/// <summary>
		/// Sends or updates the timeout of this Aura (if exposed to client)
		/// </summary>
		protected internal void SendTimeout(int value)
		{
			if (!m_hideFromClient)
			{
				AuraHandler.SendAuraUpdate(m_auras.Owner, this);
			}
		}

		/// <summary>
		/// Removes all of this Aura's occupied fields
		/// </summary>
		protected void RemoveFromClient()
		{
			if (m_hideFromClient)
				return;

			AuraHandler.SendRemoveAura(m_auras.Owner, this);
		}
		#endregion

		protected void CallAllHandlers(HandlerDelegate dlgt)
		{
			foreach (var handler in m_handlers)
			{
				dlgt(handler);
			}
		}

		protected delegate void HandlerDelegate(AuraEffectHandler handler);

		/// <summary>
		/// Returns whether this aura's effects are stronger than those of the given effect (of the same Aura)
		/// </summary>
		public bool IsStrongerThan(AuraEffectHandler handler)
		{
			var handlers = m_handlers;
			foreach (var oldHandler in handlers)
			{
				if (handler.GetType() == oldHandler.GetType())
				{
					return oldHandler.IsStrongerThan(handler);
				}
			}
			return true;
		}

		public AuraEffectHandler GetHandler(AuraType type)
		{
			foreach (var handler in Handlers)
			{
				if (handler.SpellEffect.AuraType == type)
				{
					return handler;
				}
			}
			return null;
		}

		public void ReApplyEffects()
		{
			CallAllHandlers(handler => handler.Remove(false));
			CallAllHandlers(handler => handler.Apply());
		}

		#region IUpdatable

		public void Update(float dt)
		{
			if (m_timer != null)
			{
				m_timer.Update(dt);
			}
		}

		#endregion

		#region Procs
		public ProcTriggerFlags ProcTriggerFlags
		{
			get { return m_spell.ProcTriggerFlags; }
		}

		public Spell ProcSpell
		{
			get { return m_spell; }
		}

		/// <summary>
		/// Chance to proc in %
		/// </summary>
		public uint ProcChance
		{
			get { return m_spell.ProcChance > 0 ? m_spell.ProcChance : 100; }
		}

		/// <summary>
		/// The amount of times that this Aura has been applied
		/// </summary>
		public int RemainingCharges
		{
			get
			{
				return m_RemainingCharges;
			}
			set
			{
				m_RemainingCharges = value;
			}
		}

		public bool CanBeTriggeredBy(Unit target, IUnitAction action)
		{
			if (m_spell.CanProcBeTriggeredBy(action))
			{
				return true;
			}
			return false;
		}

		public void TriggerProc(Unit target, IUnitAction action)
		{
			var proced = false;
			if (!m_spell.Attributes.Has(SpellAttributes.MovementImpairing) &&
				!m_spell.AttributesEx.Has(SpellAttributesEx.Negative))
			{
				// normal proc trigger
				var hasProcEffects = m_spell.ProcTriggerEffects != null;
				if (!m_spell.IsPassive || hasProcEffects)
				{
					foreach (var handler in m_handlers)
					{
						if (!hasProcEffects || handler.SpellEffect.IsProc)
						{
							// only trigger proc effects or all effects, if there arent any proc-specific effects
							if ((!handler.SpellEffect.HasAffectMask ||
								(action.Spell != null && action.Spell.MatchesMask(handler.SpellEffect.AffectMask))))
							{
								// only trigger if no AffectMask is set or the triggerer matches the proc mask
								handler.OnProc(target, action);
								proced = true;
							}
						}
					}
				}
			}
			else
			{
				proced = true;
				if (m_RemainingCharges == 0)
				{
					// proc simply breaks movement impairing or negative effect
					Remove(true);
					return;
				}
			}

			// consume a charge
			if (proced && m_RemainingCharges > 0)
			{
				m_RemainingCharges--;
				if (m_RemainingCharges == 0)
				{
					Remove(false);
				}
				else
				{
					AuraHandler.SendAuraUpdate(m_auras.Owner, this);
				}
			}
		}

		public void Dispose()
		{
			Remove(false);
		}
		#endregion

		public override string ToString()
		{
			return "Aura " + m_spell + ": " + (IsBeneficial ? "Beneficial" : "Harmful") +
				" [TimeLeft: " + TimeSpan.FromMilliseconds(TimeLeft) + "]" +
				(m_controller != null ? (" Controlled by: " + m_controller) : "");
		}

		class AuraEnumerator : IEnumerator<Aura>
		{
			public void Dispose()
			{
			}

			public bool MoveNext()
			{
				return false;
			}

			public void Reset()
			{
			}

			public Aura Current
			{
				get { return null; }
			}

			object IEnumerator.Current
			{
				get { return null; }
			}
		}
	}
}
